package RelCcompiler;

import java.util.*;  
import java.util.regex.*;
import java.io.*;

import PrimitiveDataStructures.PrimitiveDS;
import RelUtilityClasses.DecompositionGraph;
import RelUtilityClasses.ToolBox;
import RelUtilityClasses.SemanticError;
import RelUtilityClasses.DecompositionGraph.Edge;
import RelUtilityClasses.DecompositionGraph.Vertex;

/**
 * 
 * The RelC compiler main class.
 * 
 * @author Yevgeny Levanzov & Daniel Samuelov
 * 
 */
public class CompilerRelC {

	private String relFilePath;
	private String decompFilePath;

	// set of columns.
	private List<String> columns;

	// set of columns of domain of relation spec. function.
	private List<String> funcDomainCols;

	// set of columns of image of relation spec. function.
	private List<String> funcImageCols;

	// the decomposition graph.
	private DecompositionGraph decompGraph;
	
	
	//patterns for parsing.
	private static final String SET = "[{](.*)[}]";
	private static final Pattern SET_PAT = Pattern.compile(SET);

	private static final String FUNC = "(.*)[-][>](.*)";
	private static final Pattern FUNC_PAT = Pattern.compile(FUNC);

	private static final String ELEMENT = "[ \t]*(\\w+)[ \t]*";
	private static final Pattern ELEMENT_PAT = Pattern.compile(ELEMENT);
	
	private static final String GRAPH_LINE = "(\\w+)[:](.*)";
	private static final Pattern GRAPH_LINE_PAT = Pattern.compile(GRAPH_LINE);

	private static final String EDGE = ELEMENT + "[<](.*)[|][ \t]*([a-z]+)[ \t]*[>][ \t]*";
	private static final Pattern EDGE_PAT = Pattern.compile(EDGE);
	
	
	/**
	 * A constructor.
	 * 
	 * @param relFilePath path to relation spec. file
	 * @param decompFilePath path to decomposition graph file
	 * 
	 */
	public CompilerRelC(String relFilePath, String decompFilePath) throws SyntaxError, SemanticError {

		this.relFilePath = relFilePath;
		this.decompFilePath = decompFilePath;

		columns = new ArrayList<String>();
		funcDomainCols = new ArrayList<String>();
		funcImageCols = new ArrayList<String>();
		
		decompGraph = new DecompositionGraph();

		parseColumnsAndFunction();

		parseDecompGraph();
		
		semanticAndAdequacyChecks();

		createCodeFile(); //creating the output java code file.
	}

	
	//parses set of columns and function file.
	private void parseColumnsAndFunction() throws SyntaxError, SemanticError {

		BufferedReader reader = null;

		try {

			reader = new BufferedReader(new FileReader(relFilePath));

			// first line - the columns set.
			String line = reader.readLine();

			Matcher m = SET_PAT.matcher(line);

			if (!m.matches()) {
				throw new SyntaxError("Wrong syntax of set of columns.");
			}

			// filling columns list.
			stringOfColsToList(m.group(1), columns);

			// second line - the function.
			line = reader.readLine();

			m = FUNC_PAT.matcher(line);

			if (!m.matches()) {
				throw new SyntaxError("Wrong syntax of functional dependency.");
			}

			// filling function domain columns list.
			stringOfColsToList(m.group(1), funcDomainCols);

			// filling function image columns list.
			stringOfColsToList(m.group(2), funcImageCols);

			reader.close();
			
		} catch (FileNotFoundException e) {

			System.err.println("ERROR: File " + relFilePath + " was not found.");

		} catch (IOException e) {

			System.err.println("ERROR: Could not read from file " + relFilePath + ".");

		} finally {

			// try to close the file.
			try {

				if (reader != null) {
					reader.close();
				}

			} catch (IOException e) {

				System.err.println("ERROR: Could not close the file " + relFilePath + ".");
			}
		}

	} // end of parseColumnsAndFunction().

	
	
	// gets a comma-separated string of columns, and makes from it a list of columns.
	private void stringOfColsToList(String s, List<String> lst) throws SyntaxError {

		String[] cols = s.split(",");

		Matcher m = null;

		for (String c : cols) {

			m = ELEMENT_PAT.matcher(c);

			if (!m.matches()) {
				throw new SyntaxError("Wrong syntax of name of a column.");
			}

			lst.add(m.group(1));
		}
	}
	
	
	//parses the decomposition graph file.
	private void parseDecompGraph() throws SyntaxError {

		BufferedReader reader = null;

		try {

			reader = new BufferedReader(new FileReader(decompFilePath));

			String line = null;

			while (true) {

				line = reader.readLine();

				if (line == null) { // end of file.
					break;
				}

				parseGraphFileLine(line);
			}
			
			reader.close();
			
			calcBoundColumns();  //calculates bound columns for each vertex.
			

		} catch (FileNotFoundException e) {

			System.err.println("ERROR: File " + decompFilePath + " was not found.");

		} catch (IOException e) {

			System.err.println("ERROR: Could not read from file " + decompFilePath + ".");

		} finally {

			// try to close the file.
			try {

				if (reader != null) {
					reader.close();
				}

			} catch (IOException e) {

				System.err.println("ERROR: Could not close the file " + decompFilePath + ".");
			}
		}

	} // end of parseDecompGraph().

	
	//parses a line of decomposition graph file.
	private void parseGraphFileLine(String line) throws SyntaxError {
		
		Matcher m = GRAPH_LINE_PAT.matcher(line);

		if (!m.matches()) {
			throw new SyntaxError("Wrong syntax of line in graph file.");
		}
		
		String origVertexName = m.group(1);
		
		//the vertex of this line.
		DecompositionGraph.Vertex v = new DecompositionGraph.Vertex(origVertexName);
		
		//this vertex is a sink, we get his columns.
		if (!m.group(2).contains("<")) {
			
			List<String> cols = new ArrayList<String>();
			stringOfColsToList(m.group(2), cols);
			v.setSinkColsNames(cols);
			
			this.decompGraph.addVertex(v);
			
			return;
		}
		
		//this vertex has outgoing edges.
		String[] adjacentVert = m.group(2).split(";");
		
		for (String s : adjacentVert) {
			
			m = EDGE_PAT.matcher(s);
			
			if (!m.matches()) {
				throw new SyntaxError("Wrong syntax of line in graph file.");
			}
			
			String adjVertName = m.group(1);
			
			PrimitiveDS ds = PrimitiveDS.valueOf(m.group(3).toUpperCase(Locale.ENGLISH));
			
			//getting columns names.
			List<String> cols = new ArrayList<String>();
			stringOfColsToList(m.group(2), cols);
			
			DecompositionGraph.Edge e = new DecompositionGraph.Edge(origVertexName, adjVertName, cols, ds);
			
			v.addAdj(e);
		}

		this.decompGraph.addVertex(v);
	}

	
	//semantic and adequacy checks for input. 
	private void semanticAndAdequacyChecks() throws SemanticError {
		
		if (!ToolBox.isSubSet(funcDomainCols, columns) || !ToolBox.isSubSet(funcImageCols, columns)) {
			throw new SemanticError("Function's domain and image columns must be a sub-set of relational columns.");
		}
		
		List<String> union = ToolBox.setsUnion(this.funcDomainCols, this.funcImageCols);
		Collections.sort(union);
		Collections.sort(columns);
		
		if (!ToolBox.isDisjointSets(funcDomainCols, funcImageCols) && !union.equals(columns)) {
			
			throw new SemanticError("Function's domain and image columns must be disjoint sets " +
											"and their union should be all relational columns.");
		}
		
		
		/////////////////////////////////////////////
		
		//checking if decomp. graph is DAG.
		
		Iterator<Vertex> itr = this.decompGraph.iterator();
		if (itr == null) {
			throw new SemanticError("Decomposition graph isn't DAG.");
		}
		
		//checking if graph has single root.
		List<String> vertNames = new ArrayList<String>();  //all vertices.
		
		for (Vertex v : this.decompGraph) {
			vertNames.add(v.getName());
		}
		
		//now we remove all vertices with in-degree > 0.
		for (Vertex v : this.decompGraph) {
			for (Edge e : v.getAdjList()) {
				vertNames.remove(e.getDestVertName());
			}
		}
		
		if (vertNames.size() > 1) {
			throw new SemanticError("Decomposition graph has more than one root.");
		}

		//check that all columns are represented.
		Set<String> cols = new TreeSet<String>();  
		
		for (Vertex v : this.decompGraph) {
			
			if (v.isSinkVertex()) {
				cols.addAll(v.getSinkColsNames());
				continue;
			}
			
			for (Edge e : v.getAdjList()) {
				cols.addAll(e.getCols());
			}
		}
		
		if (cols.size() != this.columns.size()) {
			throw new SemanticError("Not all columns represented in decomposition graph.");
		}
		
		//checking that root isn't a unit decomposition.
		if (this.decompGraph.iterator().next().isSinkVertex()) {
			throw new SemanticError("Root of decomposition graph can't be a unit decomposition.");
		}
	}
	
	
	//sets for each vertex the set of columns which appear on any path from root to him (union).
	private void calcBoundColumns() {
		
		Iterator<DecompositionGraph.Vertex> itr = this.decompGraph.iterator();
		
		//we travel on vertices in topo. order, to fill the cols. correctly.
		while(itr.hasNext()) {
			
			Vertex v = itr.next();
			
			for (Edge e : v.getAdjList()) {
				
				Vertex dest = decompGraph.getVertexByName(e.getDestVertName());
				
				List<String> tmp = dest.getBoundCols();
				
				tmp = ToolBox.setsUnion(tmp, v.getBoundCols());
				tmp = ToolBox.setsUnion(tmp, e.getCols());
				
				if (dest.isSinkVertex()) {
					tmp = ToolBox.setsUnion(tmp, dest.getSinkColsNames());
				}

				dest.setBoundCols(tmp);
			}
			
		}  //end of while.
		
	}
	
	
	//creates the output java code file.
	private void createCodeFile() {
			
		String outputCodeDir = ".\\src\\OutputCode";
		String graphSerializFileName = "decompSeri.txt";
		String outClassName = "RelImpl";
		
		File f = new File(outputCodeDir);  //make package of output file.
		f.mkdirs();
	
		String cols = lstToString(this.columns);
		String funDomCols = lstToString(this.funcDomainCols);
		String funImgCols = lstToString(this.funcImageCols);
		
		BufferedReader reader = null;
		Writer writer = null;
		ObjectOutputStream out = null;

		try {

			//serialization of decomposition graph.
			out = new ObjectOutputStream(new FileOutputStream(".\\src\\RelUtilityClasses\\" + graphSerializFileName));
			out.writeObject(this.decompGraph);

			///////////////////////////////////////////////
			
			reader = new BufferedReader(new FileReader(".\\src\\RelCcompiler\\CompilerOutputCore.java"));
			writer = new FileWriter(new File(outputCodeDir + "\\" + outClassName + ".java")); 

			writer.write("package OutputCode;\r\n");  //writing package line.
			
			String line = reader.readLine();  //skip package line.
			line = reader.readLine();
			
			while (!line.startsWith("public class")) { //till class line.
				
				writer.write(line + "\r\n");
				
				// reads next line.
				line = reader.readLine();

			} 

			writer.write("public class " + outClassName + " {\r\n");   //writing class line.
			
			///////////////////////////////////////////////
			
			line = reader.readLine();
			
			while (!line.contains("*/")) { //till end of java-doc of constructor.
				
				writer.write(line + "\r\n");
				
				// reads next line.
				line = reader.readLine();

			}
			
			writer.write("\t */\r\n");
			
			///////////////////////////////////////////////
			
			//writing the constructor code.
			
			String nl = "\r\n\r\n";
			
			String ctorCode = "\tpublic " + outClassName + "() {" + nl +
							  "\t\tdecompSerializFilePath = " + 
							  "\".\\\\src\\\\RelUtilityClasses\\\\" + graphSerializFileName + "\";" + nl +
							  "\t\tcols = " + "\"" + cols + "\";" + nl +
							  "\t\tfunDomCols = " + "\"" + funDomCols + "\";" + nl +
							  "\t\tfunImgCols = " + "\"" + funImgCols + "\";" + nl +
							  "\t\t" + "root = null;" + nl +
							  "\t\t" + "initColumsAndFunction();" + nl +
							  "\t\t" + "initDecompGraph();" + nl +
							  "\t}" + nl;
							  

			writer.write(ctorCode);
			
			///////////////////////////////////////////////
			
			line = reader.readLine();
			
			while (line != null) {  //till EOF.
				
				writer.write(line + "\r\n");
				
				// reads next line.
				line = reader.readLine();
			}
	
			reader.close();
			writer.close();
			
			
		} catch (FileNotFoundException e) {

			//we never get here.
			System.err.println("ERROR: File was not found.");

		} catch (IOException e) {

			System.err.println("ERROR: Could not write/read to/from file.");

		} finally {

			// try to close the files.
			try {

				if (writer != null) {
					writer.close();
				}
				
				if (reader != null) {
					reader.close();
				}
				
				
				if (out != null) {
					out.close();
				}

			} catch (IOException e) {

				System.err.println("ERROR: Could not close file.");
			}
		}
			
	}
	
	
	
	//converts a list of strings to a comma-separated string.
	private String lstToString(List<String> lst) {
		
		String ret = "";
		
		if (lst.isEmpty()) {
			return ret;
		}
		
		for (String s : lst) {
			
			ret = ret + s + ",";
		}
		
		return ret.substring(0, ret.length()-1);  //without last comma.
	}
	
}
